package com.credithc.customers.base.utils;

import com.alibaba.fastjson.JSON;
import com.credithc.customers.base.exception.CustomerException;
import org.apache.commons.lang.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.*;

/**
 * @Title：Http请求工具类
 * @Author：guobingbing
 * @Date 2018/8/31 17:59
 * @Description
 * @Version
 */
public final class HttpClientUtil {
    /**
     * 日志
     */
    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
    /**
     * 连接超时时间
     */
    private static final int connTimeout = 30 * 1000;
    /**
     * 等待响应时间
     */
    private static final int socketTimeout = 30 * 1000;

    private static CloseableHttpClient httpClient = null;

    private final static Object syncLock = new Object();

    /**
     * 配置
     *
     * @param httpRequestBase
     */
    private static void config(HttpRequestBase httpRequestBase) {
        // 配置请求的超时设置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connTimeout)
                .setConnectTimeout(connTimeout).setSocketTimeout(socketTimeout).build();
        httpRequestBase.setConfig(requestConfig);
    }

    /**
     * 获取HttpClient对象
     *
     * @param
     * @return org.apache.http.impl.client.CloseableHttpClient
     * @author guobingbing
     * @date 2018/1/23 16:15
     */
    private static CloseableHttpClient getHttpClient(String url) {
        String hostname = url.startsWith("http") ? url.split("/")[2] : url.split("/")[0];
        int port = 80;
        if (hostname.contains(":")) {
            String[] arr = hostname.split(":");
            hostname = arr[0];
            port = Integer.parseInt(arr[1]);
        }
        if (httpClient == null) {
            synchronized ( syncLock ) {
                if (httpClient == null) {
                    httpClient = createHttpClient(200, 40, 100, hostname, port);
                }
            }
        }
        return httpClient;
    }

    /**
     * 创建HttpClient对象
     *
     * @param
     * @return org.apache.http.impl.client.CloseableHttpClient
     * @author guobingbing
     * @date 2018/1/23 16:16
     */
    private static CloseableHttpClient createHttpClient(int maxTotal, int maxPerRoute, int maxRoute, String hostname, int port) {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory>create().register("http", plainsf)
                .register("https", sslsf).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        // 将最大连接数增加
        cm.setMaxTotal(maxTotal);
        // 将每个路由基础的连接增加
        cm.setDefaultMaxPerRoute(maxPerRoute);
        HttpHost httpHost = new HttpHost(hostname, port);
        // 将目标主机的最大连接数增加
        cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
        // 请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            /**
             * 是否重试
             * @param exception
             * @param executionCount
             * @param context
             * @return
             */
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= 3) {// 如果已经重试了5次，就放弃
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                    return false;
                }
                if (exception instanceof InterruptedIOException) {// 超时
                    return false;
                }
                if (exception instanceof UnknownHostException) {// 目标服务器不可达
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                    return false;
                }
                if (exception instanceof SSLException) {// SSL握手异常
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm)
                .setRetryHandler(httpRequestRetryHandler).build();
        return httpClient;
    }

    /**
     * 直接post数据
     *
     * @param url
     * @param sendData  发送的报文
     * @param headerMap 请求头
     * @param chartset  字符集
     * @return java.lang.String
     * @author guobingbing
     * @date 2018/8/31 18:10
     */
    public static String postData(String url, String sendData, Map<String, String> headerMap, String chartset) throws Exception {
        String result = "";
        CloseableHttpResponse resp = null;
        try {
            if (StringUtils.isBlank(chartset)) {
                chartset = "utf-8";
            }
            CloseableHttpClient client = getHttpClient(url);
            HttpPost httpPost = new HttpPost(url);
            config(httpPost);

            httpPost.addHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
            if (headerMap != null && !headerMap.isEmpty()) {
                for (String name : headerMap.keySet()) {
                    httpPost.addHeader(name, headerMap.get(name));
                }
            }
            httpPost.addHeader("Accept-Charset", chartset);
            StringEntity entity = new StringEntity(sendData, chartset);
            entity.setContentEncoding(chartset);
            entity.setContentType("application/x-www-form-urlencoded");
            entity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded"));
            httpPost.setEntity(entity);
            resp = client.execute(httpPost);
            if (resp.getStatusLine().getStatusCode() == 200) {
                HttpEntity he = resp.getEntity();
                if (he != null) {
                    result = EntityUtils.toString(he, chartset);
                }
            } else {
                handlerHttpStatusCode(resp.getStatusLine().getStatusCode());
            }
        } catch (Exception e) {
            logger.error("postData", e);
            throw e;
        } finally {
            try {
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                logger.error("post", e);
            }
        }
        return result;
    }

    /**
     * 通过HttpConnection发送数据
     *
     * @param
     * @return java.lang.String
     * @author guobingbing
     * @date 2017/12/1 18:50
     */
    public static String httpConnection(String url, String data) throws Exception {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(data);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            logger.error("httpConnection", e);
            throw e;
        }
        //使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                logger.error("httpConnection", e);
            }
        }
        return result;
    }

    /**
     * post json格式数据
     *
     * @param url
     * @param jsonData  json数据
     * @param headerMap 头部数据
     * @param chartset  字符集
     * @return java.lang.String
     * @author guobingbing
     * @date 2018/8/31 18:11
     */
    public static String postJSON(String url, String jsonData, Map<String, String> headerMap, String chartset) throws Exception {
        String result = "";
        CloseableHttpResponse resp = null;
        try {
            if (StringUtils.isBlank(chartset)) {
                chartset = "utf-8";
            }
            CloseableHttpClient client = getHttpClient(url);
            HttpPost httpPost = new HttpPost(url);
            config(httpPost);

            httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
            if (headerMap != null && !headerMap.isEmpty()) {
                for (String name : headerMap.keySet()) {
                    httpPost.addHeader(name, headerMap.get(name));
                }
            }
            StringEntity entity = new StringEntity(jsonData, chartset);
            entity.setContentEncoding(chartset);
            entity.setContentType("text/json");
            entity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            httpPost.setEntity(entity);
            resp = client.execute(httpPost);
            if (resp.getStatusLine().getStatusCode() == 200) {
                HttpEntity he = resp.getEntity();
                result = EntityUtils.toString(he, chartset);
            } else {
                handlerHttpStatusCode(resp.getStatusLine().getStatusCode());
            }
        } catch (Exception e) {
            logger.error("post", e);
            throw e;
        } finally {
            try {
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                logger.error("post", e);
            }
        }
        return result;
    }

    /**
     * post 表单格式数据
     *
     * @param url
     * @param paramMap  键值对
     * @param headerMap 头部数据
     * @param chartset  字符集
     * @return java.lang.String
     * @author guobingbing
     * @date 2018/8/31 18:13
     */
    public static String postFormData(String url, Map<String, String> paramMap, Map<String, String> headerMap, String chartset) throws Exception {
        String result = "";
        CloseableHttpResponse resp = null;
        try {
            if (StringUtils.isBlank(chartset)) {
                chartset = "utf-8";
            }
            CloseableHttpClient client = getHttpClient(url);
            HttpPost httpPost = new HttpPost(url);
            config(httpPost);
            httpPost.addHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
            if (headerMap != null && headerMap.size() > 0) {
                Iterator<String> iter = headerMap.keySet().iterator();
                while (iter.hasNext()) {
                    String key = iter.next();
                    String value = headerMap.get(key);
                    httpPost.addHeader(key, value);
                }
            }
            List<NameValuePair> paramList = new ArrayList<>();
            if (paramMap != null && paramMap.size() > 0) {
                Iterator<String> iter = paramMap.keySet().iterator();
                while (iter.hasNext()) {
                    String key = iter.next();
                    String value = paramMap.get(key);
                    paramList.add(new BasicNameValuePair(key, value));
                }
            }
            HttpEntity entity = new UrlEncodedFormEntity(paramList, chartset);
            httpPost.setEntity(entity);
            resp = client.execute(httpPost);
            if (resp.getStatusLine().getStatusCode() == 200) {
                HttpEntity he = resp.getEntity();
                result = EntityUtils.toString(he, chartset);
            } else {
                handlerHttpStatusCode(resp.getStatusLine().getStatusCode());
            }
        } catch (Exception e) {
            logger.error("postData", e);
            throw e;
        } finally {
            try {
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                logger.error("post", e);
            }
        }
        return result;
    }

    /**
     * @param url
     * @param paramMap  键值对
     * @param headerMap 头部数据
     * @param chartset  编码
     * @param destFile  目标文件目录
     * @return
     * @throws Exception
     */
    public static String postFormDataForFile(String url, Map<String, String> paramMap, Map<String, String> headerMap, String chartset, String destFile) throws Exception {
        boolean result = true;
        CloseableHttpResponse resp = null;
        InputStream inputStream = null;
        try {
            if (StringUtils.isBlank(chartset)) {
                chartset = "utf-8";
            }
            CloseableHttpClient client = getHttpClient(url);
            HttpPost httpPost = new HttpPost(url);
            config(httpPost);
            httpPost.addHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
            if (headerMap != null && headerMap.size() > 0) {
                Iterator<String> iter = headerMap.keySet().iterator();
                while (iter.hasNext()) {
                    String key = iter.next();
                    String value = headerMap.get(key);
                    httpPost.addHeader(key, value);
                }
            }
            List<NameValuePair> paramList = new ArrayList<>();
            if (paramMap != null && paramMap.size() > 0) {
                Iterator<String> iter = paramMap.keySet().iterator();
                while (iter.hasNext()) {
                    String key = iter.next();
                    String value = paramMap.get(key);
                    paramList.add(new BasicNameValuePair(key, value));
                    System.out.println("key:" + key);
                    System.out.println(value);
                }
            }
            HttpEntity entity = new UrlEncodedFormEntity(paramList, chartset);
            httpPost.setEntity(entity);
            resp = client.execute(httpPost);

            if (resp.getStatusLine().getStatusCode() == 200) {
                HttpEntity he = resp.getEntity();
                inputStream = he.getContent();
                File file = new File(destFile);
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                int l = -1;
                byte[] tmp = new byte[1024];
                while ((l = inputStream.read(tmp)) != -1) {
                    fileOutputStream.write(tmp, 0, l);
                }
                fileOutputStream.flush();
                fileOutputStream.close();
            } else {
                result = false;
            }
        } catch (Exception e) {
            logger.error("postData", e);
            throw e;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            try {
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                logger.error("post", e);
                result = false;
            }
        }

        /**
         * 模拟json返回,以便统一处理
         */
        Map map = new HashMap();
        map.put("result", result);
        map.put("destFile", destFile);
        return JSON.toJSONString(map);
    }

    /**
     * Get
     *
     * @param url
     * @param headerMap
     * @return java.lang.String
     * @author guobingbing
     * @date 2018/8/31 18:14
     */
    public static String httpGet(String url, Map<String, String> headerMap) throws Exception {
        String result = "";
        CloseableHttpResponse resp = null;
        try {
            CloseableHttpClient client = getHttpClient(url);
            HttpGet httpGet = new HttpGet(url);
            config(httpGet);
            if (headerMap != null && !headerMap.isEmpty()) {
                for (String name : headerMap.keySet()) {
                    httpGet.addHeader(name, headerMap.get(name));
                }
            }
            resp = client.execute(httpGet);
            if (resp.getStatusLine().getStatusCode() == 200) {
                HttpEntity he = resp.getEntity();
                result = EntityUtils.toString(he);
            } else {
                handlerHttpStatusCode(resp.getStatusLine().getStatusCode());
            }
        } catch (Exception e) {
            logger.error("http get", e);
            throw e;
        } finally {
            try {
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                logger.error("http get", e);
            }
        }
        return result;
    }

    private static void handlerHttpStatusCode(int statusCode) throws Exception {
        throw new CustomerException(statusCode, ("HTTP " + statusCode + " ERROR"));
    }
}
